// Copyright (C) 2010 Razvan Constantin <razvan.constantin@rconst.net>
//
// This file is part of FileBow.
//
// FileBow is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FileBow is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FileBow.  If not, see <http://www.gnu.org/licenses/>.

#include "logic/path-tree.h"

#include <string>
#include <stack>
#include <Poco/Path.h>

#include "common/common.h"

using namespace std;
using Poco::Path;

namespace filebow {

////////////////////////////////////////////////////////////////////////////////
// PathNode
////////////////////////////////////////////////////////////////////////////////

PathNode::PathNode(Type nodeType, const string& nodeName)
    : type(nodeType), name(nodeName), mSize(0), mParent(NULL) {}

PathNode::~PathNode() {}

PathNode* PathNode::Insert(PathNode* newChild) {
  CHECK_PTR(newChild);
  PathNode* last;
  for (PathNode* p = newChild; p; p = p->sibling()) {
    CHECK_NULL(p->mParent);
    p->mParent = this;
    ++mSize;
    last = p;
  }
  if (mChild.get()) {
    last->mSibling.reset(mChild.release());
  }
  mChild.reset(newChild);
  return newChild;
}

void PathNode::Remove() {
  if (!mParent) {
    return;
  }
  mParent->mSize--;
  if (mParent->mChild.get() == this) {
    mParent->mChild.release();
    mParent->mChild.reset(mSibling.release());
  } else {
    PathNode* p = mParent->mChild.get();
    while (p && p->sibling() != this) {
      p = p->sibling();
    }
    CHECK_PTR(p);
    p->mSibling.release();
    p->mSibling.reset(mSibling.release());
  }
  mParent = NULL;
}

Poco::Path MakePath(const PathNode& node) {
  stack<string> revPath;
  for (const PathNode* p = &node; p && !IsRoot(*p); p = p->parent()) {
    revPath.push(p->name);
  }
  Poco::Path path;
  for (; !revPath.empty(); revPath.pop()) {
    path.makeDirectory();
    path.setFileName(revPath.top());
  }
  return path;
}

Poco::Path SafeMakePath(const PathNode* node) {
  return node ? MakePath(*node) : Poco::Path();
}

////////////////////////////////////////////////////////////////////////////////
// PathTree::Node::Helper
////////////////////////////////////////////////////////////////////////////////

class PathTree::Node::Helper {
  public: Helper(PathTree::Node* parentNode);
  public: Node* operator*();
  public: Node* operator->();
  public: void operator++();
  public: void operator++(int);
  public: void Next();
  public: PathTree::Node* Find(Node* child);
  public: PathTree::Node* Find(const string& name);
  public: PathTree::Node* Insert(PathTree::Node* newChild);
  public: void Remove();

  private: PathTree::Node* node;
  private: PathTree::Node* prevChild;
  private: PathTree::Node* currentChild;
};

////////////////////////////////////////////////////////////////////////////////
// PathTree
////////////////////////////////////////////////////////////////////////////////

static PathTree::Walker staticNoopWalker;

PathTree::PathTree()
    : rootNode(new Node), noopWalker(&staticNoopWalker) {
  rootNode->nodeType = PathType::ROOT;
}

PathTree::~PathTree() {
  delete rootNode;
}

PathTree::Node* PathTree::Next(Node* pathNode, Walker* walker) const {
  CHECK_PTR(pathNode);
  if (!walker) {
    walker = this->walker();
  }
  if (!walker->SkipDir() && pathNode->firstChild()) {
    walker->VisitNode(pathNode->firstChild());
    return pathNode->firstChild();
  }
  for (Node* node = pathNode; node && node != root(); node = node->parent()) {
    CHECK(node->type() != PathType::ROOT);
    if (node->type() == PathType::DIR) {
      walker->ExitDir();
    }
    if (walker->SkipDir()) {
      continue;
    }
    if (node->nextSibling()) {
      walker->VisitNode(node->nextSibling());
      return node->nextSibling();
    }
  }
  return NULL;
}

PathTree::Node* PathTree::Insert(const Path& path, Node* parentNode) {
  CHECK_PTR(parentNode);
  CHECK(parentNode == root() || !path.isAbsolute());
  Node* node = parentNode;
  for (int i = 0; i < path.depth(); ++i) {
    Node::Helper helper(node);
    if (!helper.Find(path.directory(i))) {
      Node* newNode = helper.Insert(new Node());
      newNode->nodeType = PathType::DIR;
      newNode->nodeName = path.directory(i);
    }
    node = *helper;
  }
  if (path.isFile()) {
    Node::Helper helper(node);
    if (!helper.Find(path.getFileName())) {
      Node* newNode = helper.Insert(new Node());
      newNode->nodeType = PathType::FILE;
      newNode->nodeName = path.getFileName();
    }
    node = *helper;
  }
  return node;
}

void PathTree::Remove(Node* pathNode) {
  CHECK_PTR(pathNode);
  CHECK_PTR(pathNode->parent());
  Node::Helper helper(pathNode->parent());
  if (helper.Find(pathNode)) {
    helper.Remove();
  }
  delete pathNode;
}

PathTree::Node* PathTree::Find(const Path& path) {
  Node* node = root();
  for (int i = 0; node && i < path.depth(); ++i) {
    node = Node::Helper(node).Find(path.directory(i));
  }
  if (node && path.isFile()) {
    node = Node::Helper(node).Find(path.getFileName());
  }
  return node;
}

Path PathTree::GetPath(const Node* pathNode) {
  stack<string> pathStack;
  for (const Node* node = pathNode; node && node->type() != PathType::ROOT; node = node->parent()) {
    pathStack.push(node->name());
  }
  Path path("/");
  for (; !pathStack.empty(); pathStack.pop()) {
    path.pushDirectory(pathStack.top());
  }
  if (pathNode->type() == PathType::FILE) {
    path.makeFile();
  }
  return path;
}

////////////////////////////////////////////////////////////////////////////////
// PathTree::Node
////////////////////////////////////////////////////////////////////////////////

PathTree::Node::Node()
    : nodeType(PathType::NONE),
    parentNode(NULL), childNode(NULL), siblingNode(NULL) {
  // Empty.
}

PathTree::Node::~Node() {
  if (childNode) {
    delete childNode;
  }
  if (siblingNode) {
    delete siblingNode;
  }
}

////////////////////////////////////////////////////////////////////////////////
// PathTree::Node::Helper
////////////////////////////////////////////////////////////////////////////////

PathTree::Node::Helper::Helper(Node* parentNode)
    : node(parentNode), prevChild(NULL) {
  currentChild = node->firstChild();
}

PathTree::Node* PathTree::Node::Helper::operator*() {
  return currentChild;
}

PathTree::Node* PathTree::Node::Helper::operator->() {
  return currentChild;
}

void PathTree::Node::Helper::operator++() {
  Next();
}

void PathTree::Node::Helper::operator++(int) {
  Next();
}

void PathTree::Node::Helper::Next() {
  prevChild = currentChild;
  currentChild = currentChild->nextSibling();
}

PathTree::Node* PathTree::Node::Helper::Find(PathTree::Node* child) {
  for (; currentChild && currentChild != child; Next()) {
    // Empty.
  }
  return currentChild;
}

PathTree::Node* PathTree::Node::Helper::Find(const string& name) {
  for (; currentChild && currentChild->name() != name; Next()) {
    // Empty.
  }
  return currentChild;
}

PathTree::Node* PathTree::Node::Helper::Insert(Node* newChild) {
  CHECK_NULL(newChild->parent());
  CHECK_NULL(newChild->nextSibling());
  if (prevChild) {
    newChild->siblingNode = prevChild->siblingNode;
    prevChild->siblingNode = newChild;
  } else {
    CHECK_NULL(node->firstChild());
    node->childNode = newChild;
  }
  newChild->parentNode = node;
  currentChild = newChild;
  return currentChild;
}

void PathTree::Node::Helper::Remove() {
  if (prevChild) {
    prevChild->siblingNode = currentChild->siblingNode;
  } else {
    node->childNode = currentChild->siblingNode;
  }
  currentChild->parentNode = NULL;
  currentChild->siblingNode = NULL;
  currentChild = NULL;
}

////////////////////////////////////////////////////////////////////////////////
// PathTree::Iterator
////////////////////////////////////////////////////////////////////////////////

PathTree::Iterator::Iterator(PathTree* tree) {
  Init(tree, tree->root(), NULL);
}

PathTree::Iterator::Iterator(PathTree* tree, Node* node) {
  Init(tree, node, NULL);
}

PathTree::Iterator::Iterator(PathTree* tree, Walker* walker) {
  Init(tree, tree->root(), walker);
}

PathTree::Iterator::Iterator(PathTree* tree, Node* node, Walker* walker) {
  Init(tree, node, walker);
}

void PathTree::Iterator::Init(PathTree* tree, Node* node, Walker* walker) {
  this->tree = tree;
  this->currentNode = node;
  this->walker = walker;
}

PathTree::Iterator::~Iterator() {
  // Empty
}

void PathTree::Iterator::Next() {
  currentNode = tree->Next(currentNode, walker);
}

void PathTree::Iterator::VisitAll() {
  while (!done()) {
    this->operator++();
  }
}

////////////////////////////////////////////////////////////////////////////////
// PathTree::Walker
////////////////////////////////////////////////////////////////////////////////

PathTree::Walker::Walker() {
  // Empty.
}

PathTree::Walker::~Walker() {
  // Empty.
}

void PathTree::Walker::EnterDir(const PathTree::Node* dirNode) {
  // Empty.
}

bool PathTree::Walker::SkipDir() {
  return false;
}

void PathTree::Walker::ExitDir() {
  // Empty.
}

void PathTree::Walker::VisitFile(const PathTree::Node* fileNode) {
  // Empty.
}

void PathTree::Walker::VisitNode(const PathTree::Node* node) {
  if (node->type() == PathType::FILE) {
    VisitFile(node);
  } else if (node->type() == PathType::DIR) {
    EnterDir(node);
  }
}

bool IsFile(const PathTree::Node& node) {
  return node.type() == PathType::FILE;
}

bool IsDir(const PathTree::Node& node) {
  return node.type() == PathType::DIR;
}


} // namespace filebow
